package games.eightPuzzle.states;

import genetics.GeneticInterface;
import games.eightPuzzle.heuristics.Coordinate;
import games.eightPuzzle.heuristics.DummyHeuristic;
import games.eightPuzzle.heuristics.Heuristic;

import java.util.ArrayList;
import java.util.Scanner;
import java.io.*;
/**
 * this class represents a puzzle state
 */
public class SlidingPuzzleState implements Comparable<SlidingPuzzleState>, GeneticInterface{
	
	
	private int _rows, _cols; // number of rows/cols
	private int[][] _puzzle;  // the puzzle at the current state
	
	private Heuristic _h; //the heuristic to evaluate the distance
						  //if this state to the goal state
	
	private Heuristic _totalFunction; //the total ranking function
	
	private int _hCost; //the ranking of the heuristic function
	private int _depth; //the depth of this state from the start state
	
	private int _totalRanking; //the rank of the totalFunction
	
	private SlidingPuzzleState _parent = null; //the parent of this state
	
	//the position of the holes in this state
	private ArrayList<Coordinate> _holes = new ArrayList<Coordinate>();
	
	//the hash code of this state
	private String _hashCode;


	
	
	/*
	* Read the state from a file.
	*/
	public void loadStateFromFile(String fileName)
	{
		try
		{
			File f = new File(fileName);
			Scanner input = new Scanner(f);
			_rows = input.nextInt();
			_cols = input.nextInt();
			_puzzle = new int[_rows][_cols];
			for (int r_idx = 0; r_idx < _rows; r_idx++)
				for (int c_idx = 0; c_idx<_cols; c_idx++)
				{
					_puzzle[r_idx][c_idx] = input.nextInt();
					if (_puzzle[r_idx][c_idx] == 0){
						_holes.add(new Coordinate(r_idx,c_idx));
					}
				
				}
			
		}
		catch (Exception e)
		{
			System.out.println(e.getMessage());
			System.exit(-1);
		}
	}
	
	//copy constructor
	public SlidingPuzzleState(SlidingPuzzleState other){
		_rows = other._rows;
		_cols = other._cols;
		_depth = other._depth;
		_parent = other;
		_hCost = other._hCost;
		_totalRanking = other._totalRanking;
		_h = other._h;
		_totalFunction = other._totalFunction;
		_puzzle = new int[_rows][_cols];
		
		for (int i=0;i<_rows;i++){
			for (int j=0;j<_cols;j++){
				_puzzle[i][j] = other._puzzle[i][j];
			}
		}
		
		for(Coordinate c : other._holes){
			_holes.add(new Coordinate(c));
		}

		_hashCode = other._hashCode;
	}
	
	/*
	* Constructor, load the state from a file
	* // Complete this code!
	*/
	public SlidingPuzzleState (String fileName) 
	{
		loadStateFromFile(fileName);
		makeHash();
	}
	
	
	
	//getters of the state's attributes
	
	public int getDepth(){
		return _depth;
	}

	public String getHash(){
		return _hashCode;
	}
	
	public int getHCost(){
		return _hCost;
	}
	
	public int getNumRows()
	{
		return _rows;
	}
	
	public int getNumCols()
	{
		return _cols;
	}
	
	public ArrayList<Coordinate> getHolesList(){
		return _holes;
	}
	
	public int getTotalRanking(){
		return _totalRanking;
	}
	
	public long getSlideHRank(){
		return _depth;
	}
	
	//get the value at the puzzle matrix
	public int get(int r, int c)
	{
		return _puzzle[r][c];
	}
	
	//get the trace from the start state to this state
	public ArrayList<SlidingPuzzleState> getTrace(){
		ArrayList<SlidingPuzzleState> trace = new ArrayList<SlidingPuzzleState>();
		SlidingPuzzleState tmp = this;
		while (tmp!=null){
			trace.add(0,tmp);
			tmp = tmp._parent;
		}
		return trace;
	}
	
	public SlidingPuzzleState getFather(){
		return _parent;
	}
	public int getHolesNum(){
		return _holes.size();
	}
	
	
	//set the heuristic function and the total function
	public void setHeuristics(Heuristic h,Heuristic totalFunction){
		_h = h;
		_totalFunction = totalFunction;
	}
	
	//this method generate hash code for this slide
	public void makeHash(){
		String tmp = "";
		
		for(int i=0;i<_rows;i++){
			for(int j=0;j<_cols;j++){
				tmp+=String.valueOf(_puzzle[i][j])+"#";
			}
		}
		_hashCode = tmp;
	}
	public void setDepth(int depth){
		_depth = depth;
	}
	
	public void setFather(SlidingPuzzleState father){
		_parent = father;
	}
	
	public String toString()
	{
		String s = "";
	
		for (int r_idx = 0; r_idx < _rows; r_idx++)
		{
			for (int c_idx = 0; c_idx < _cols; c_idx++)
			{
				int i = _puzzle[r_idx][c_idx];
				String t = "";
				if (i<10)
					t=" ";
				s+= _puzzle[r_idx][c_idx]+" "+t;
			}
			s+="\n";
		}
		return s+"TotalRanking: " + String.valueOf(_totalRanking)+"\n";
	}

	
	public void dumpToScreen()
	{
		System.out.println(toString());
	}
	
	/*
	*   Write this state to a BufferedWriter. You should create the BufferWriter
	*   yourself, and close it before the program finish running.
	*/
	public void print(BufferedWriter bw)
	{

	    String s = toString()+"\n";
		try
		{
			bw.write(s);
		}
		catch (Exception e)
		{
			System.out.println(e.getMessage());
			System.exit(-1);
		}
	}
	
	
	/**
	 * this method move a given hole to a given position
	 * and update the ranking and the hash of the state
	 * assumption: the given hole can move to the given
	 * direction
	 * @param holeNum the number of the hole to move
	 * @param d the direction to move to hole to
	 * @return this
	 */
	private SlidingPuzzleState moveHole(int holeNum,Dir d){
		Coordinate h = _holes.get(holeNum);
		Coordinate c = new Coordinate(h);
		switch (d){
		case UP: h.setI(h.getI()-1); break;
		case DOWN: h.setI(h.getI()+1); break;
		case LEFT: h.setJ(h.getJ()-1); break;
		case RIGHT: h.setJ(h.getJ()+1); break;
		}
		
		
		//update the puzzle matrix
		_puzzle[c.getI()][c.getJ()] = _puzzle[h.getI()][h.getJ()]; 
		_puzzle[h.getI()][h.getJ()] = 0;
		//recalculate/update the state costs
		_depth++;
		_hCost = _h.rank(this);
		_totalRanking = _totalFunction.rank(this);
		//regenerate hash code
		makeHash();
		
		return this;
	}
	
	
	
	/**
	 * this function calculate possible next moves from this state
	 * @return an ArrayList of states
	 */
	public ArrayList<SlidingPuzzleState> getPossibleNextMoves(){
		ArrayList<SlidingPuzzleState> moves = new ArrayList<SlidingPuzzleState>();
		int holeNum = 0;
		for (Coordinate h: _holes){
			
			//for every hole, check if its not on the edges
			//of the puzzle and if his next position is not
			//already a hole
			
			if (h.getI() > 0 && _puzzle[h.getI()-1][h.getJ()]!=0){
				
				moves.add(new SlidingPuzzleState(this).moveHole(holeNum, Dir.UP));
			}
			
			if (h.getI() < (_rows-1) && _puzzle[h.getI()+1][h.getJ()]!=0){
				moves.add(new SlidingPuzzleState(this).moveHole(holeNum, Dir.DOWN));
			}
			
			if (h.getJ() > 0 && _puzzle[h.getI()][h.getJ()-1]!=0){
				moves.add(new SlidingPuzzleState(this).moveHole(holeNum, Dir.LEFT));
			}
			
			if (h.getJ() < (_cols-1) && _puzzle[h.getI()][h.getJ()+1]!=0){
				moves.add(new SlidingPuzzleState(this).moveHole(holeNum, Dir.RIGHT));
			}
			holeNum++;
		}
		return moves;
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(SlidingPuzzleState other) {
		if (_totalRanking == other._totalRanking) return 0;
		if (_totalRanking > other._totalRanking) return 1;
		return -1;
	}

	//rank the state
	public void rank(){
		_depth = 0;
		_hCost = _h.rank(this);
		_totalRanking = _totalFunction.rank(this);
	}
	
	/**
	 * @param other
	 * @return true iff the states are equals (their puzzle matrix is the same)
	 */
	public boolean equals(SlidingPuzzleState other){
		return other.getHash().equals(getHash()); 
	}
	
	public static void main(String[] args){
		SlidingPuzzleState s = new SlidingPuzzleState("I:\\Devel\\AI\\ex1\\finalState.txt");
		Heuristic h = new DummyHeuristic();
		s.setHeuristics(h, h);
		for(SlidingPuzzleState ns: s.getPossibleNextMoves()){
			ns.dumpToScreen();
			System.out.println();
		}
	}

	@Override
	public String hash() {
		return getHash();
	}
	
	public int weight(){
		return 1;
	}

}